#include <linux/limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>
#include <string.h>

#include <stdexcept>

#include "string-util.h"
#include "filepath-util.h"

using std::string;

auto PathUtil::getcwd() -> string
{
    string buf;
    getcwd(buf);
    return buf;
}

auto PathUtil::getcwd(string &buf) -> void
{
    auto old_size = buf.size();
    buf.resize(old_size + PATH_MAX);
    char *ptr = const_cast<char*>(buf.c_str() + old_size);
    auto ret = ::getcwd(ptr,PATH_MAX);
    if (ret == NULL)
        throw std::runtime_error("");
    buf.resize(old_size + strlen(ret));
    return ;
}


auto PathUtil::absolute(const char *path,const char *rel) -> string
{
    string buf;
    absolute(buf,path,rel);
    return buf;
}

auto PathUtil::absolute(string &buf,const char *path,const char *rel) -> void
{
    auto *end = path + strlen(path);
    do {
        --end;
    } while (*end == '/');
    ++end;
    if (isAbsolute(path)) {
        buf.append(path,end);
    } else {
        if (rel == 0) {
            getcwd(buf);
        } else {
            if (!isAbsolute(rel)) {
                getcwd(buf);
                if (buf.back() != '/')
                    buf.append(1,'/');
            }
            buf.append(rel);
        }
        if (buf.back() != '/')
            buf.append(1,'/');
        buf.append(path,end);
    }
    return ;
}

auto PathUtil::realpath(const char *path) -> string
{
    string str;
    realpath(str,path);
    return str;
}

auto PathUtil::realpath(string &buf,const char *path) -> void
{
    // [rpath,rpath_limit) 确定了输出缓冲区.
    // dest 指向者输出缓冲区中某个字节.
    // 输出缓冲区中的路径总是绝对路径,并且不带有末尾'/'.
    // 若输出缓冲区为空,当且仅当输出缓冲区中仅存放着根目录.
    char *rpath = NULL,*rpath_limit = NULL;
    char *dest = NULL;
    char *extra_buf = NULL;
    auto old_size = buf.size();
    buf.reserve(old_size + PATH_MAX);
    int num_links = 0;
    // 符号链接的最大数量,LINUX 内核与 GLIBC 使用的值是 40.
    constexpr int num_links_max = 33;

    // 初始化 rpath,rpath_limit,dest 的取值.
    if (!isAbsolute(path)) {
        getcwd(buf);
        buf.resize(old_size + PATH_MAX);
        rpath = const_cast<char*>(buf.c_str() + old_size);
        rpath_limit = rpath + PATH_MAX + 1;
        if (strcmp(rpath,"/") == 0) {
            *rpath = '\0';
            dest = rpath;
        } else {
            dest = static_cast<char*>(rawmemchr(rpath,'\0'));
        }
    } else {
        buf.resize(old_size + PATH_MAX);
        rpath = const_cast<char*>(buf.c_str() + old_size);
        rpath_limit = rpath + PATH_MAX + 1;
        *rpath = '\0';
        dest = rpath;
    }

    // 循环,处理 path 中每一个 component.
    const char *start = NULL, *end = NULL;
    for (start = end = path; *start != '\0'; start = end) {
        while (*start == '/')
            ++start;
        for (end = start; *end != '\0' && *end != '/'; ++end)
            ;
        // 此时 [start,end) 确定了 path 中的一个 component,开始对 component 进行处理
        auto component_size = end - start;
        if (component_size == 0)
            break;
        if (component_size == 1 && *start == '.') {
            continue;
        } else if (component_size == 2 && start[0] == '.' && start[1] == '.') {
            if (*rpath != '\0') {
                char *last_slash_ptr = static_cast<char*>(memrchr(rpath,'/',dest - rpath));
                dest = last_slash_ptr;
                *dest = '\0';
            }
        } else {
            if (dest + 1 + component_size >= rpath_limit) {
                auto dest_offset = dest - rpath;
                buf.resize(dest_offset + 1 + component_size + old_size);
                rpath = const_cast<char*>(buf.c_str() + old_size);
                dest = rpath + dest_offset;
                rpath_limit = dest + 1 + component_size + 1;
            }
            *dest = '/';
            ++dest;
            dest = static_cast<char*>(mempcpy(dest,start,component_size));
            *dest = '\0';
            struct stat rpath_stat;
            int ret = lstat(rpath,&rpath_stat);
            if (ret == -1 && errno != ENOENT) {
                throw std::runtime_error(format("lstat(%s):%s",rpath,strerror(errno)));
            } else if (ret != -1) {
                if (S_ISLNK(rpath_stat.st_mode)) {
                    char *buf = static_cast<char*>(alloca(PATH_MAX));
                    if (++num_links > num_links_max)
                        throw std::runtime_error("符号链接过多");
                    auto symlink_size = ::readlink(rpath,buf,PATH_MAX - 1);
                    if (symlink_size < 0)
                        throw std::runtime_error(format("readlink(%s):%s",rpath,strerror(errno)));
                    buf[symlink_size] = '\0';
                    if (!extra_buf)
                        extra_buf = static_cast<char*>(alloca(PATH_MAX));
                    size_t end_size = strlen(end);
                    if (end_size + symlink_size >= PATH_MAX)
                        throw std::runtime_error("文件名过长");
                    memmove(&extra_buf[symlink_size],end,end_size + 1);
                    path = end = static_cast<char*>(memcpy(extra_buf,buf,symlink_size));

                    if (isAbsolute(path)) {
                        *rpath = '\0';
                        dest = rpath;
                    } else {
                        if (*rpath != '\0') {
                            char *last_slash_ptr = static_cast<char*>(memrchr(rpath,'/',dest - rpath));
                            dest = last_slash_ptr;
                            *dest = '\0';
                        }
                    }
                } else if (!S_ISDIR(rpath_stat.st_mode) && *end) {
                    throw std::runtime_error(format("%s 不是目录",rpath));
                }
            }
        }
    }
    if (*rpath == '\0') {
        buf.resize(old_size);
        buf.append(1,'/');
    } else {
        buf.resize(old_size + (dest - rpath));
    }
    return ;
}

auto PathUtil::isDir(const char *path) -> bool
{
    struct stat path_stat;
    int ret = stat(path,&path_stat);
    if (ret != 0) {
        if (errno == ENOENT)
            return false;
        throw std::runtime_error(format("无法获取路径 %s 的属性信息;错误: %s",
                                        path,strerror(errno)));
    }
    if (!S_ISDIR(path_stat.st_mode))
        return false;
    return true;
}

auto PathUtil::readlink(string &buf,const char *path) -> void
{
    auto old_size = buf.size();
    buf.resize(old_size + PATH_MAX);
    int readlink_ret = ::readlink(path,const_cast<char*>(buf.c_str() + old_size),PATH_MAX);
    if (readlink_ret < 0)
        throw std::runtime_error(format("readlink(%s):%s",path,strerror(errno)));
    buf.resize(old_size + readlink_ret);
    return ;
}

auto PathUtil::readlink(const char *path) -> string
{
    string buf;
    readlink(buf,path);
    return buf;
}

